#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <assert.h>
#include <sys/time.h>
#include "2d5p_aceMesh.h"
#include "aceMesh_runtime_c.h"


double A[2][SIZEX][SIZEY];
double test[2][SIZEX][SIZEY];
#define alpha_d 0.0876
#define beta_d  0.0765
unsigned long slave_counts[64]={0};

unsigned long rpcc()
{
  unsigned long time;
  asm("rtc %0":"=r"(time):);
  return time;
}

#ifdef  _PROFILING
unsigned long slave_comp_count[64]={0};
unsigned long slave_dma_count[64]={0};
unsigned long slave_count[64]={0};
unsigned long total_slave_count=0;
unsigned long total_dma_count=0;
unsigned long total_comp_count=0;
unsigned long slave_up[64]={0};
unsigned long slave_up1[64]={0}; //only halo
unsigned long slave_down[64]={0};
unsigned long total_up=0;
unsigned long total_up1=0;
unsigned long total_down=0;
int reuse_num[64]={0};
#endif

unsigned short template88[64]={
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63
};

unsigned short template84[32]={
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31
};
unsigned short template44[16]={0,1,2,3,  8,9,10,11,  16,17,18,19,  24,25,26,27};
unsigned short template42[8]={0,1,2,3,   8,9,10,11};
unsigned short template22[4]={0,1,8,9};
unsigned short template2[2]={0,1};
unsigned short template1[1]={0};

unsigned short tpl_x,tpl_y; //template shape
unsigned short *my_template; 

extern void  slave_stencil_core(targs* args);
void my_neighbors(void** neighbor_addrs,int* num_neigh, targs* args)
{
  int starti = args->starti;
  int startj = args->startj;
  void* src_addr=args->src_addr;
  *num_neigh=0;
  if(starti >= BLKX)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr - BLKX * SIZEY * 8));
  if(starti < SIZEX - BLKX)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr + BLKX * SIZEY * 8));
  if(startj >= BLKY)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr - BLKY * 8));
  if(startj < SIZEY - BLKY)
     neighbor_addrs[(*num_neigh)++] = ((void*)((unsigned long)src_addr + BLKY * 8));
}
/*int Do2d5p(const int iter, const int num_threads)
{
  targs each;
  int i,j,ii,itr; 
  //register traditionally
  //and each template is sparse
  //BAD
  //0000 1111 2222 3333
  //0000 1111 2222 3333
  //0000 1111 2222 3333
  //TODO:
  //CANNOT HAVE GOOD PERFORMANCE
  return 0;
}
*/

#ifdef OLD_REGISTER_DENSE_TEMPLATE
int Do2d5p(const int iter, const int num_threads)
{
  targs each;
  int i,j,ii,itr; 
  //tasks are registerd in traditionl order
  //each template is contiguous
  //But their task objs are not allocated contiguously
  //
  for( itr = 0; itr < iter; ++itr)
  {
    acemesh_begin_split_task("3d7p iter"); 
    //template shape =<tpl_x, tpl_y >

     for (i = 1; i < SIZEX-1; i += BLKX)
     {   
       int endi = (i + BLKX > SIZEX - 1 ) ? SIZEX - 1 : i + BLKX;
       ii=i-i%(BLKX*tpl_x);
       for (j = 1; j < SIZEY-1; j += BLKY)
       { 
          int jj=j-j%(BLKY*tpl_y);  
          short affinity_index1=(i-ii)/BLKX%tpl_x;
          short affinity_index2=(j-jj)/BLKY%tpl_y;
          int affinityid=my_template[affinity_index1*tpl_y+affinity_index2];
          int endj = (j + BLKY > SIZEY -1 ) ? SIZEY - 1 : j + BLKY;
         
          //printf("<%d,%d>:%d,    ",affinity_index1,affinity_index2,affinityid);
         
          each.starti = i;
          each.endi = endi;
          each.startj = j;
          each.endj = endj;
          each.iter = itr;
          each.src_addr=&A[(itr+1)%2][i][j];
          acemesh_push_wlist(1, &A[itr%2][i][j],NORMAL);
          acemesh_push_rlist(1, &A[(itr+1)%2][i][j], NORMAL);
          acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          acemesh_task_set_type(STENCIL_TASK);
          acemesh_task_set_affinity(affinityid);
       }      
     }
     //printf("\n");
     acemesh_end_split_task();
  }
  acemesh_spawn_and_wait(1);
  return 0;
}
#endif

//#ifdef DENSE_TILE_DENSE_MALLOC
int Do2d5p(const int iter, const int num_threads)
{
  targs each;
  int i,j,ii, jj, itr; 
  //each template is contiguous
  //their task objs are also allocated ontiguous
  //
  for( itr = 0; itr < iter; ++itr)
  {
    acemesh_begin_split_task("3d7p iter"); 
    //template shape =<tpl_x, tpl_y >

    for (ii = 1; ii < SIZEX-1; ii += BLKX*tpl_x)
    for (jj = 1; jj < SIZEY-1; jj += BLKY*tpl_y)
 
     for(i = ii; i < min(ii+BLKX*tpl_x, SIZEX -1); i += BLKX)
     {   
       int endi = (i + BLKX > SIZEX - 1 ) ? SIZEX - 1 : i + BLKX;
       for(j = jj; j < min(jj+BLKY*tpl_y, SIZEY -1); j += BLKY)
       {   
          short affinity_index1=(i-ii)/BLKX;//%tpl_x;
          short affinity_index2=(j-jj)/BLKY;//%tpl_y;
          int affinityid=my_template[affinity_index1*tpl_y+affinity_index2];
          int endj = (j + BLKY > SIZEY -1 ) ? SIZEY - 1 : j + BLKY;
          /*
          printf("<%d,%d>:%d,    ",affinity_index1,affinity_index2,affinityid);
          */
          assert((affinity_index1>-1) && (affinity_index1<tpl_x));
          assert((affinity_index2>-1) && (affinity_index2<tpl_y));
          each.starti = i;
          each.endi = endi;
          each.startj = j;
          each.endj = endj;
          each.iter = itr;
          each.src_addr=&A[(itr+1)%2][i][j];
          acemesh_push_wlist(1, &A[itr%2][i][j],NORMAL);
          acemesh_push_rlist(1, &A[(itr+1)%2][i][j], NORMAL);
          acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                                                (void*)(&each), sizeof(targs),
                                                 NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
          acemesh_task_set_type(STENCIL_TASK);
          acemesh_task_set_affinity(affinityid);
       }      
     }
     //printf("\n");
     acemesh_end_split_task();
  }
  printf("begin exec %d\n",itr);
  acemesh_spawn_and_wait(1);
  return 0;
}
//#endif

void init()
{
   int i,j;
   for(i = 0; i < SIZEX; i++)
     for(j = 0; j < SIZEY; j++)
     {    
        A[1][i][j] = (double)(i*2.5 + j*3.3) /3.0;
        A[0][i][j]=A[1][i][j];
     }
}
void get_template(int num_threads,unsigned short* tp1,unsigned short* tp2, unsigned short** template)
{
switch (num_threads)
{
case 64: //8X8
    *tp1=8,*tp2=8;
    *template=template88;
    break;
case 32: //8X4
    *tp1=8,*tp2=4;
    *template=template84;
    break;
case 16:  /*4X4*/
    *tp1=4,*tp2=4;
    *template=template44;
    break;
case 8:  //4X2
    *tp1=2,*tp2=4;
    *template=template42;
    break;
case 4:   //2X2
    *tp1=2,*tp2=2;
    *template=template22;
    break;
case 2:   //1X2
    *tp1=1,*tp2=2;
    *template=template2;
    break;
default:  //1
    *tp1=1,*tp2=1;
    *template=template1;
    break;
}
    return;
}
int check(const int iter)
{
   int i,j,k;
   int itr;
   int ii,jj,kk;
   printf("begin check\n");
    for(i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
     {
       test[1][i][j] = (double)(i*2.5 + j*3.3) /3.0; 
       test[0][i][j]=test[1][i][j];
     }
    for(itr = 0; itr < iter; ++itr)
     for(i = 1; i < SIZEX - 1; i++)
      for( j = 1; j < SIZEY - 1; j++)
      {
          test[itr%2][i][j] = alpha_d * test[(itr+1)%2][i][j]+\
                         beta_d * (test[(itr+1)%2][i-1][j] + test[(itr+1)%2][i+1][j] +\
                         test[(itr+1)%2][i][j-1] + test[(itr+1)%2][i][j+1]);
      }
    for(ii = 1; ii < SIZEX - 1; ii++)
      for(jj = 1; jj < SIZEY - 1; jj++)
          if( fabs(test[(iter+1)%2][ii][jj] - A[(iter+1)%2][ii][jj]) > 1e-4 ){
             printf("i: %d   j:  %d \n",ii,jj);    
             printf("test: %20.20f",test[(iter+1)%2][ii][jj]);
             printf("\nA: %20.20f",A[(iter+1)%2][ii][jj]);
             printf("\nno");               
             return -1;
         }
  printf("correct\n");
  return 0;
}

int main(int argc, char** argv)
{
    int ITER,num_threads,real_num_threads;
    int i,j,k;
    unsigned long st,et;
    int total_reuse=0;
    int p=1;
    //while(p==1);
    ITER = 100;
    num_threads = 1;

    if(argc > 1)
       num_threads = atoi(argv[1]);
    if(argc > 2)
       ITER = atoi(argv[2]);
    if(((SIZEX-2)%BLKX) != 0 || ((SIZEY-2)%BLKY) != 0)
    {
      printf("(SIZEX-2)%BLKX) and ((SIZEY-2)%BLKY) must == 0 \n");
      assert(0);
    }
    init();
    if(num_threads!=1  || num_threads!=2  || num_threads!=4  
       || num_threads!=8 || num_threads!=16
       || num_threads!=32 || num_threads!=64)
      //not support other num_threads yet!
      assert(1);
    switch(num_threads){
    case 4:
      real_num_threads=10;
      break;
    case 8: 
      real_num_threads=12;
      break;
    case 16:
      real_num_threads=28;
      break;
    default: //1,2,32,64 
      real_num_threads=num_threads;
      break;
    }  
    acemesh_runtime_init(real_num_threads);
    get_template(num_threads,&tpl_x,&tpl_y,&my_template);
    if(num_threads!=1  || num_threads!=2  || num_threads!=4  
       || num_threads!=8 || num_threads!=16
       || num_threads!=32 || num_threads!=64)
      //not support other num_threads yet!
      assert(1);
    printf("template:%d,%d\n",tpl_x,tpl_y);
    for(i=0;i<num_threads;i++)
     printf("%d,",my_template[i]); 
    printf("\n");
    st=rpcc();
    //while(p);
    Do2d5p(ITER, num_threads);
    et=rpcc();
#ifdef _PROFILING
    for(i=0;i<real_num_threads;++i)
    {
      total_slave_count+=slave_count[i];
      total_dma_count+=slave_dma_count[i];
      total_comp_count+=slave_comp_count[i];
      total_up+=slave_up[i];
      total_up1+=slave_up1[i];
      total_down+=slave_down[i];
    //  total_reuse+=reuse_num[i];
    //  printf("%ld\n",slave_dma_count[i]);
    }
    printf("aver_slave_time=%4.2f\naver_dma_time=%4.2f\naver_comp_time=%4.2f\ntotal_reuse=%d\n",
             (float)total_slave_count/FREQ/num_threads,
             (float)total_dma_count/FREQ/num_threads,
             (float)total_comp_count/FREQ/num_threads,
             total_reuse);
    printf("elements up: %ld, halo_up %ld,down %ld\n",total_up,total_up1,total_down);
#endif
    printf("total_time:%4.2f\n",(float)(et-st)/FREQ);       
    aceMesh_runtime_shutdown(); 
#ifdef CHECK
    if(!check(ITER))
        printf("check correct!\n");
    else 
    {
        printf("check_error\n");
        exit(1);
    }
#endif
    return 0;
}
